package ewu

import ewu._

/**
 * Diese Klasse stellt den Plusoperator zur Verfuegung.
 * Es werden die beiden Expressions "left" sowie "right"
 * verwendet.
 * Beispiel: left + right
 */

case class Plus(left: Expression,
                right: Expression) extends Operator {
  def evaluate(env: Environment): Option[BigInt]
  = (left.evaluate(env), right.evaluate(env)) match {
    case (Some(le), Some(ri)) => Some(le + ri)
    case _ => None
  }

  override def toString: String = left + "+" + right

  def reduce: Expression = (left.reduce, right.reduce) match {
    case (n: Number, e) if n.value == 0 => e
    case (e, n: Number) if n.value == 0 => e
    case (le: Number, ri: Number)
      => Number(le.value + ri.value)
    case (le, ri) if le equals ri
      => Mult(Number(2), le).reduce
    case (le, ri)
      => reducePlus(le, ri)
  }

  private def reducePlus (left: Expression,
                          right: Expression): Expression = {
    toExpression(getNumbers(getNames(
      toList(left):::toList(right), Nil), 0))
  }

  private def toList (e: Expression): List[Expression] = e match {
    case p: Plus => toList(p.left):::toList(p.right)
    case _ => e::Nil
  }

  private def getNames (list: List[Expression],
                        names: List[(BigInt, Name)]):
        (List[Expression]) = list match {
    case ((e: Name)::xs) => getNames(xs, insertName(names, (1, e)))
    case ((Mult(n: Number, e:Name))::xs)
      => getNames(xs, insertName(names, (n.value, e)))
    case ((Mult(e:Name, n: Number))::xs)
      => getNames(xs, insertName(names, (n.value, e)))
    case (x::xs)         =>  x::getNames(xs, names)
    case _               => namesToList(names)
  }

  private def insertName
    (names: List[(BigInt, Name)],
     pair: (BigInt, Name)):List[(BigInt, Name)] =
      (names, pair) match{
    case ((v1, n1)::xs, (v2, n2)) if n1 == n2
      => ((v1 + v2, n1)::xs)
    case (x::xs, _) => x::insertName(xs, pair)
    case _          => pair::Nil
  }

  private def namesToList (names: List[(BigInt, Name)]):
        List[Expression] = names match {
    case ((v, n)::xs) if v == 1 => n::namesToList(xs)
    case ((v, n)::xs) if v != 0
      => Mult(Number(v), n)::namesToList(xs)
    case (x::xs)                => namesToList(xs)
    case _                      => Nil
  }

  private def getNumbers (list: List[Expression], count: BigInt):
        (List[Expression]) = list match {
    case ((n: Number)::xs) => getNumbers(xs, count + n.value)
    case (x::xs)           => x::getNumbers(xs, count)
    case _
      => if (count != 0) Number(count)::Nil else Nil
  }

  private def toExpression (list: List[Expression]): Expression =
      list match {
    case (x::y::xs) => Plus(x, toExpression(y::xs))
    case (y::xs)    => y
    case _          => null
  }
}//end Plus